package com.fzk.utils;

import org.apache.commons.codec.digest.HmacAlgorithms;
import org.apache.commons.codec.digest.HmacUtils;

import javax.crypto.Mac;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HexFormat;

/**
 * HMAC（Hash-based Message Authentication Code，基于哈希的消息认证码）是一种用于验证消息完整性和真实性的加密技术。
 * 它结合了哈希函数（如 SHA-256、MD5 等）和一个密钥，生成一个固定长度的摘要（即 HMAC 值）。
 * <p>
 * 摘要/哈希算法可用于数据完整性校验(如文件传输)
 * hmac适用于验证消息真实性和防篡改，可用于JWT。
 * 比如HmacMD5和MD5的区别就是多了个秘钥(加盐)
 *
 * @author fzk
 * @datetime 2023-02-11 01:34:33
 */
public class MyHmac {
    public static final int MD5_Str_Len = 32;// md5字符串长度32

    // md5哈希算法
    public static String md5(byte[] bytes) {
        // 生成一个MD5加密计算摘要
        MessageDigest md = getDigester("MD5");
        // 计算md5函数
        md.update(bytes);
        // digest()最后确定返回md5 hash值，返回值为32位字符串。因为md5 hash值是16进制的hex值，实际上就是16位的字符
        // 将1个字节转换为2个16进制的字符, 0--e
        return HexFormat.of().formatHex(md.digest());
    }

    /**
     * 基于md5哈希算法的哈希验证码，需引入 apaches commons-codec 库
     *
     * @param data 待取摘要值的数据
     * @param salt key/加盐
     * @return 32位小写字符hex码的md5值
     */
    public static String hmacMd5(byte[] data, String salt) {
        return new HmacUtils(HmacAlgorithms.HMAC_MD5, salt).hmacHex(data);
    }

    public static String hmac256(byte[] data, String salt) {
        Mac mac = HmacUtils.getInitializedMac(HmacAlgorithms.HMAC_SHA_256, salt.getBytes());
        mac.update(data);
        byte[] bytes = mac.doFinal();
        return HexFormat.of().formatHex(bytes);
//        return new HmacUtils(HmacAlgorithms.HMAC_SHA_256, salt).hmacHex(data);  // 一样的
    }

    /**
     * 解析文件的MD5值
     * 经过测试，在 i7 12700F, 32GB内存, windows11配置上，解析5.78GB文件运行大约12s
     *
     * @param path 文件路劲
     * @return md5 32个16进制字符, 0-e
     * @throws IOException 可能的文件错误，如文件不存在或权限不足
     */
    public static String getFileMD5(Path path) throws IOException {
        // 生成一个MD5加密计算摘要
        MessageDigest md = getDigester("MD5");
        try (FileChannel file = FileChannel.open(path, StandardOpenOption.READ)) {
            ByteBuffer buf = ByteBuffer.allocateDirect(4096);
            while (file.read(buf) != -1) {
                buf.flip();// 翻转读取
                md.update(buf);
                buf.clear();// 清空待写入
            }
            // digest()最后确定返回md5 hash值，返回值为32位字符串。因为md5 hash值是16进制的hex值，实际上就是16位的字符
            // 将1个字节转换为2个16进制的字符, 0--e
            return HexFormat.of().formatHex(md.digest());
        }
    }

    /**
     * 摘要/哈希算法，由jdk提供
     *
     * @param algorithm MD5、SHA-1、SHA-256
     */
    private static MessageDigest getDigester(String algorithm) {
        // 生成一个摘要计算器
        try {
            return MessageDigest.getInstance(algorithm);
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        }
    }
}
